﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Ppt = Microsoft.Office.Interop.PowerPoint;

namespace ObjectModel.PowerPoint
{
    public class ObservableTextRange
    {
        ResolvableObject mObject;
        ObservableShape mObservableShape;
        Ppt.Application mApp = Globals.Application;
        Ppt.TextRange mTextRange;
        Ppt.Shape mParentShape;
        string mPreviousText;
        bool mSelected;

        public event EventHandler Selected;
        public event EventHandler Deleted;

        public ObservableTextRange(Ppt.TextRange textRange)
        {
            StoredObject = new ResolvableTextRange(textRange);
        }

        public ResolvableObject StoredObject
        {
            get
            {
                return mObject;
            }

            set
            {
                mObject = value;

                mObservableShape = new ObservableShape(ParentShape);
                mObservableShape.TextSelectionChangedWithinShape += ParentShape_TextSelectionChanged;
                mObservableShape.Deleted += ParentShape_Deleted;

                mPreviousText = TextRange.Text;
            }
        }

        [XmlIgnore]
        Ppt.TextRange TextRange
        {
            get
            {
                if (mTextRange == null)
                    mTextRange = StoredObject.Resolve() as Ppt.TextRange;

                return mTextRange;
            }
        }

        [XmlIgnore]
        Ppt.Shape ParentShape
        {
            get
            {
                if (mParentShape == null)
                    mParentShape = TextRange.GetParent<Ppt.Shape>();

                return mParentShape;
            }
        }

        void ParentShape_TextSelectionChanged(object sender, EventArgs e)
        {
            if (TextRange.Text != mPreviousText || TextRange.Length == 0)
            {
                OnDeleted();
                mSelected = false;
            }
            else if (TextRange.IntersectsWithSelection())
                OnSelected();
            else
                mSelected = false;
        }

        void OnSelected()
        {
            // Note: bearing in mind that this point in code is reached when PowerPoint raises a WindowSelectionChange
            // event, when the Select method is called PowerPoint raises this event again and there seems no way to 
            // avoid this (e.g. by temporarily unhooking events, etc). It appears that PowerPoint raises the WindowSelectionChange
            // event only when the physical selection changes. Thus is doesn't raise the event if this method is called and the physical
            // selection does not change which means unhooking events while this method is called is not
            // necessary to prevent an infinite loop. Therefore this point in code is reached twice whenever
            // the text selection changes in PowerPoint from within a shape that is already selected and I don't think anything
            // can be done about that. Strangely enough this doesn't happen when text is selected in a shape which is not
            // already the currently selected and I haven't been able to figure out why.

            if (TextRange.IsSelected()) return;

            // Define autoselection behaviour of this class' wrapped TextRange:
            // If any part of the TextRange is selected, the selection will be automatically extended
            // to the whole TextRange.
            // If the TextRange is already selected and the cursor is moved to the start of the
            // TextRange (e.g. by pressing the Left key) the cursor will be automatically moved
            // one character to the left.
            // The aim of this behaviour is to ensure that the TextRange is either wholly selected
            // or not selected at all.

            Ppt.TextRange selectedTextRange = mApp.ActiveWindow.Selection.TextRange;

            // If cursor is at the start of the TextRange move it one character to the left.
            if (selectedTextRange.Start == TextRange.Start && selectedTextRange.Length == 0 &&
                    mSelected)
                TextRange.GetParent<Ppt.TextFrame>().TextRange.Characters(TextRange.Start, 0).Select();
            // If cursor is at the end of the TextRange move it one character to the right.
            else if (selectedTextRange.Start + selectedTextRange.Length == TextRange.Start + TextRange.Length &&
                    mSelected)
                TextRange.GetParent<Ppt.TextFrame>().TextRange.Characters(TextRange.Start + TextRange.Length, 0).Select();
            // Otherwise select the whole TextRange.
            else
            {
                TextRange.Select();
                mSelected = true;

                if (Selected != null)
                    Selected(this, new EventArgs());
            }
        }

        void ParentShape_Deleted(object sender, EventArgs e)
        {
            OnDeleted();
        }

        void OnDeleted()
        {
            mObservableShape.TextSelectionChangedWithinShape -= ParentShape_TextSelectionChanged;
            mObservableShape.Deleted -= ParentShape_Deleted;

            if (Deleted != null)
                Deleted(this, new EventArgs());
        }
    }
}
